package UserProfile;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;
import com.hp.hpl.jena.ontology.DatatypeProperty;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.ObjectProperty;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

import IMDBQuery.MovieIMDB;

import Recomendadores.Movie;


public class UserOntologyManager{

	/**Agrega la pelicula al perfil-ontolgia de usuario
	 * 	 
	 * @param model modelo del perfil-ontologia
	 * @param movieIMDb pelicula que se agregara al perfil
	 * @param calificacion calificacion que el usuario dio a la pelicula, tambien llamado DOI
	 * @param conf configuracion del sistema
	 * @param isTest boolean que indica si la pelicula es de test 
	 */
	public static OntModel fillOntology(OntModel model, MovieIMDB movieIMDB, Calificacion calificacion, Configuration conf, boolean isTest) {

		//obtengo el namespace del modelo ontologico
		String NS = conf.getNS();

		//Creacion de la pelicula como subclase de su primer genero
		LinkedList<String> ll = movieIMDB.getGenres(); //obtengo los generos de la pelicula
		int numElems = ll.size();
		if(!ll.isEmpty()){
			String g = ll.get(0).toUpperCase().trim(); //obtengo el primer genero
			OntClass genre = model.getOntClass(NS + g);
			Individual movieOntology = model.createIndividual( NS + movieIMDB.getID(), genre); //creo la pelicula como instancia de su primer genero

			//obtengo algunas propiedades de la pelicula y las cargo en el modelo
			DatatypeProperty title = model.getDatatypeProperty( NS +"hasTitle" );
			DatatypeProperty id = model.getDatatypeProperty( NS +"hasID" );
			DatatypeProperty doi = model.getDatatypeProperty(NS +"hasDOI");
			DatatypeProperty ranking = model.getDatatypeProperty(NS +"hasRanking");
			DatatypeProperty votes = model.getDatatypeProperty(NS +"hasVotes");

			movieOntology.setPropertyValue(id, model.createTypedLiteral(movieIMDB.getID()));
			movieOntology.setPropertyValue(title, model.createTypedLiteral(movieIMDB.getTitleIMDB()));
			movieOntology.setPropertyValue(ranking, model.createTypedLiteral(movieIMDB.getRating().getRank()));
			movieOntology.setPropertyValue(votes, model.createTypedLiteral(movieIMDB.getRating().getVotes()));
			//si es una pelicula Test no se le asigna DOI, porque sera predecido por el modulo recomendador
			if(!isTest){
				movieOntology.setPropertyValue(doi, model.createTypedLiteral(calificacion.getRating())); 			
			}
                        
			/*Pelicula como instancia de los otros generos (en caso de que los tenga)
			 */
			for (int i=1;i<numElems;i++) {
				String c = ll.get(i).toUpperCase().trim();
				genre = model.getOntClass(NS + c);
				movieOntology.addOntClass(genre);
			}

			/*Creo las "DepictedCoordinates" y los "Credits" (ObjectProperties)
			 */
			addAllIndividuals(movieIMDB, movieOntology, model, NS);
		}else{
			System.out.println("la pelicula" +movieIMDB.getTitleIMDB()+ "no tiene generos");
		}

		return model;			

	}
/**Agrega la pelicula a la ontología de peliculas
	 * 	 
	 * @param model modelo del perfil-ontologia
	 * @param movieIMDb pelicula que se agregara al perfil
	 * @param conf configuracion del sistema
	 */

public static OntModel fillOntology(OntModel model, MovieIMDB movieIMDB, Configuration conf) {

		//obtengo el namespace del modelo ontologico
		String NS = conf.getNS();

		//Creacion de la pelicula como subclase de su primer genero
		LinkedList<String> ll = movieIMDB.getGenres(); //obtengo los generos de la pelicula
		int numElems = ll.size();
		if(!ll.isEmpty()){
			String g = ll.get(0).toUpperCase().trim(); //obtengo el primer genero
			OntClass genre = model.getOntClass(NS + g);
			Individual movieOntology = model.createIndividual( NS + movieIMDB.getID(), genre); //creo la pelicula como instancia de su primer genero

			//obtengo algunas propiedades de la pelicula y las cargo en el modelo
			DatatypeProperty title = model.getDatatypeProperty( NS +"hasTitle" );
			DatatypeProperty id = model.getDatatypeProperty( NS +"hasID" );
			DatatypeProperty doi = model.getDatatypeProperty(NS +"hasDOI");
			DatatypeProperty ranking = model.getDatatypeProperty(NS +"hasRanking");
			DatatypeProperty votes = model.getDatatypeProperty(NS +"hasVotes");

			movieOntology.setPropertyValue(id, model.createTypedLiteral(movieIMDB.getID()));
			movieOntology.setPropertyValue(title, model.createTypedLiteral(movieIMDB.getTitleIMDB()));
			movieOntology.setPropertyValue(ranking, model.createTypedLiteral(movieIMDB.getRating().getRank()));
			movieOntology.setPropertyValue(votes, model.createTypedLiteral(movieIMDB.getRating().getVotes()));
			
			/*Pelicula como instancia de los otros generos (en caso de que los tenga)
			 */
			for (int i=1;i<numElems;i++) {
				String c = ll.get(i).toUpperCase().trim();
				genre = model.getOntClass(NS + c);
				movieOntology.addOntClass(genre);
			}

			/*Creo las "DepictedCoordinates" y los "Credits" (ObjectProperties)
			 */
			addAllIndividuals(movieIMDB, movieOntology, model, NS);
		}else{
			System.out.println("la pelicula" +movieIMDB.getTitleIMDB()+ "no tiene generos");
		}

		return model;			

	}


	/**Propaga el DOI de una pelicula por todas sus caracteristicas semanticas del perfil-ontolgia de usuario
	 * 	 
	 * @param model modelo del perfil-ontologia
	 * @param idUser id del usuario
	 * @param conf configuracion del sistema
     * @param usuario
	 */
	public static OntModel doiPropagation(OntModel model, int idUser, Configuration conf, boolean usuario){
		System.out.println("... propagating actors");
		//propagar DOI en los actores
		propagateInstanceDOI(model, "Actor", "isActorOf", conf);

		System.out.println("... propagating directors");
		//propagar DOI en los directores
		propagateInstanceDOI(model, "Director", "isDirectorOf", conf);

		System.out.println("... propagating writers");
		//propagar DOI en los escritores
		propagateInstanceDOI(model, "Writer", "isWriterOf", conf);

		System.out.println("... propagating years");
		//propagar DOI en los anos
		propagateInstanceDOI(model, "Year", "isYearOf", conf);

		System.out.println("... propagating genres");
		//propagar los generos		
		propagateGenersDOI(model, "ContentCS", ""+idUser, conf, usuario);	

		System.out.println("Propagation Finished");

		return model;

	}




	/**Este metodo se encarga de propagar los DOI de las peliculas por los generos
	 *  
	 * @param model modelo de la ontologia
	 * @param contentClassName nombre de la clase padre de los generos
	 * @param user id del usuario 
	 * @param conf configuracion del sistema  
	 */
	private static void propagateGenersDOI(OntModel model, String contentClassName, String user, Configuration conf, boolean usuario) {
		//si no existe la base de datos, la creo
		if(!ConnectionDB.existsDatabase(conf.getURL(),conf.getDBNAME(),conf.getDBUSER(),conf.getDBPASS())){
			ConnectionDB.createDatabase(conf.getURL(),conf.getDBNAME(),conf.getDBUSER(),conf.getDBPASS());					
		}

		//intento borrar la tabla, si existe
		//String tabla="user";
                if(!usuario)
                {
                    user="s"+user;
                }
                //ConnectionDB.dropTable(conf.getURL(), conf.getDBNAME(), conf.getDBUSER(), conf.getDBPASS(), tabla+user);
		//creo la tabla de Generos en la base de datos, esto se debe hacer porque los generos no pueden tener doi por ser clases.
		//La propiedad hasDOI solo se puede aplicar a instancias
		//ConnectionDB.createTable(conf.getURL(), conf.getDBNAME(), conf.getDBUSER(), conf.getDBPASS(), tabla+user+"(nombre VARCHAR(20),doi FLOAT)");

		//obtengo las propiedades que necesito
		Property hasDOI = model.getProperty(conf.getNS() + "hasDOI");

		OntClass contents = model.getOntClass(conf.getNS()+contentClassName);//obtengo la clase padre de los generos

		ExtendedIterator<OntClass> genres = contents.listSubClasses();//obtengo los generos
		//para cada genero promedio los doi de sus peliculas
		while(genres.hasNext()){
			OntClass gender = genres.next();
			ExtendedIterator<? extends OntResource> iterator =gender.listInstances();
			int cant = 0; //en esta variable se contaran las peliculas que tengan doi
			float partialDOI = 0; //suma los DOI de las instancias para luego promediarlas
			while (iterator.hasNext()){
				OntResource movie = iterator.next(); //obtengo el recurso de la pelicula
				RDFNode prop = movie.getPropertyValue(hasDOI); //obtengo la propiededad hasDOI 
				if (prop !=null){
					partialDOI = partialDOI + prop.asLiteral().getFloat();
					cant++;
				}
			}
			if(cant>0){
                            String genero=gender.getLocalName();
                            boolean exist=ConnectionDB.existsTable(conf.getURL(), conf.getDBNAME(), conf.getDBUSER(), conf.getDBPASS(),genero);
                            if (exist==false)
                            ConnectionDB.createTable(conf.getURL(), conf.getDBNAME(), conf.getDBUSER(), conf.getDBPASS(), genero+"(user VARCHAR(20),doi FLOAT,Num Float,PRIMARY KEY (user) )");
				float doi = partialDOI/cant;
				String values= "('"+user+"',"+doi+","+cant+")" ;
				ConnectionDB.insert(conf.getURL(),conf.getDBNAME(),conf.getDBUSER(),conf.getDBPASS(), genero, user,values);
			}
		}
	}

	/**Este metodo se encarga de propagar los DOI de las peliculas por todas sus propiedades, Actores, Directores, etc.
	 *  
	 * @param model modelo de la ontologia
	 * @param instanceClassName nombre de la clase a la que se quiere propagar el DOI
	 * @param propertyIs nombre la la propiedad is que relaciona una instancia con las peliculas de la que es propiedad	
	 * @param conf configuracion del sistema   
	 */	
	private static void propagateInstanceDOI(OntModel model, String instanceClassName, String propertyIs, Configuration conf) {
		//obtengo las propiedades que necesito
		Property hasDOI = model.getProperty(conf.getNS() + "hasDOI");
		Property is = model.getProperty(conf.getNS()+ propertyIs);

		//obtengo la clase para luego obtener sus instancias, intancias que pueden ser Actores, Actices, Directores, etc
		OntClass instanceClass = model.getOntClass(conf.getNS()+instanceClassName);
		ExtendedIterator<? extends OntResource> iterator =instanceClass.listInstances();
		while(iterator.hasNext()){
			OntResource instanceResource = iterator.next(); //obtengo el recurso de la instancia
			NodeIterator movies = instanceResource.listPropertyValues(is); //obtengo las peliculas asociadas a esta instancia
			float partialDOI = 0; //en esta variable se iran sumando los doi de las peliculas
			int cant = 0; //en esta variable se contaran las peliculas que tengan doi
			//recorro las peliculas
			while(movies.hasNext()){
				Resource movieResourse = movies.next().asResource();
				Individual movie = model.getIndividual(movieResourse.getURI());
				RDFNode propDOI = movie.getPropertyValue(hasDOI);
				//compruebo que la pelicula halla sido calificada, osea que tenga seteada la propiedad hasDOI
				if (propDOI !=null){
					partialDOI = partialDOI + propDOI.asLiteral().getFloat();
					cant++;
				}
			}
			if(cant>0){
				float doi = partialDOI/cant;
				instanceResource.setPropertyValue(hasDOI, model.createTypedLiteral(doi));
			}
		}

	}

	/**Busca el id de una <code>movie</code> en IMDb, a partir del id de la pelicula en la base de GrupLens
	 *  
	 * @param movieIDinGroupLensDataBase id de la Pelicula definido en la DB de GrupLens.
	 * @param movieDataBase url de la base de datos GroupLens de peliculas	 
	 */
	public static String getMovieID(int movieIDinGroupLensDataBase, String movieDataBase) {
		String linea;
		int idMovie;		
		String campos[];
		String idIMDB;

		try {
			FileReader fr = new FileReader(movieDataBase);
			BufferedReader bf = new BufferedReader(fr);

			//recorro la base de peliculas en busca del id requerido
			while ((linea = bf.readLine())!=null) {

				//Procesamiento de cada linea del fichero
				campos = linea.split("::");

				idMovie = Integer.parseInt(campos[0]);
				idIMDB = campos[1];

				if(idMovie == movieIDinGroupLensDataBase){
					return idIMDB;		    						    
				}
			}
			bf.close();
			return null;

		} catch (FileNotFoundException e) {
			System.out.println("UserOntologyManager.getMovieID() Error: "+e.getMessage());	        
		} catch (NumberFormatException e) {
			System.out.println("UserOntologyManager.getMovieID() Error: "+e.getMessage());
		} catch (IOException e) {
			System.out.println("UserOntologyManager.getMovieID() Error: "+e.getMessage());
		}
		return null;		
	}

	/**Obtiene las peliculas vistas por un usuario
	 * @param model modelo de la ontologia
	 * @param conf configuracion del sistema  
	 */
	public static ArrayList<Movie> getMoviesByUser(OntModel base, Configuration conf){

		ArrayList<Movie> historialDeUsuario = new ArrayList<Movie>();


		//obtengo las propiedades que necesito
		Property hasDOI = base.getProperty(conf.getNS() + "hasDOI");
		Property hasID = base.getProperty(conf.getNS() + "hasID");


		OntClass contents = base.getOntClass(conf.getNS()+"ContentCS");//obtengo la clase padre de los generos

		ExtendedIterator<OntClass> genres = contents.listSubClasses();//obtengo los generos
		ArrayList<OntClass> ordenPreliminar = new ArrayList<OntClass>();
		int count = 0; 		
		while(genres.hasNext()) {
			ordenPreliminar.add(genres.next());
			count++; //cuento los generos
		}
		Iterator<OntClass> ite1 = ordenPreliminar.iterator();

		String[] orden = new String[count];//guardo el orden de recorrida de los generos
		int i=0;
		while(ite1.hasNext()){
			String name = ite1.next().getLocalName();
			orden[i]=name;
			i++;
		}

		Iterator<OntClass> ite2 = ordenPreliminar.iterator();
		//recorro en busca de instancias de peliculas
		while(ite2.hasNext()){
			OntClass gender = ite2.next();
			ExtendedIterator<? extends OntResource> iterator =gender.listInstances();						
			while (iterator.hasNext()){
				OntResource movieResource = iterator.next(); //obtengo el recurso de la pelicula
				RDFNode prop = movieResource.getPropertyValue(hasDOI); //obtengo la propiededad hasDOI
				//si la instancia tiene doi es porque el usuario califico esa pelicula
				if (prop !=null){
					String movieID = movieResource.getPropertyValue(hasID).asLiteral().getString();
					boolean existe=false;
					for(int j=0; j<historialDeUsuario.size();j++){
						if(historialDeUsuario.get(j).getId().equals(movieID)){
							existe=true;
							insertarGenero(historialDeUsuario.get(j),gender.getLocalName());
							break;
						}
					}
					if(!existe){
						float doi = movieResource.getPropertyValue(hasDOI).asLiteral().getFloat();
						float[] generos = new float[count];
						Movie movie= new Movie(movieID,doi,generos,orden);
						historialDeUsuario.add(movie);
						insertarGenero(movie, gender.getLocalName());
					}
				}
			}
		}
		return historialDeUsuario;
	}


	private static void insertarGenero(Movie movie, String localName) {
		float[] generos = movie.getGeneros();
		String[] orden = movie.getOrden();
		for(int i=0;i<orden.length;i++){
			if(orden[i].equals(localName)) generos[i]=1;
		}
	}

	/**Obtiene una pelicula <code>Movie</code> segun el formato definido para el modulo de filtrado basado en contenido
	 * @param idMovie el id de la pelicula definido en MovieLens
	 * @param rating el DOI asociado a la pelicula para ese usuario
	 * @param userID el id del usuario
	 * @param orden el orden de los generos del vector de caracteriticas
	 * @param conf configuracion del sistema
	 */
	public static Movie getMovieModel(int idMovie, int rating, int userID, String[] orden, Configuration conf) {

		if(orden!=null){
			try {
				OntModel base = getUserModel(userID, conf);		

				String IMDbMovieID = getMovieID(idMovie, conf.getDBMOVIES());
				Individual movie = base.getIndividual( conf.getNS()+IMDbMovieID);
				if(movie!=null){
					float[] generos = new float[orden.length];
					for(int i=0;i<orden.length;i++){
						if(movie.hasOntClass(conf.getNS()+orden[i])){
							generos[i]=1;
						}else{
							generos[i]=0;
						}
					}

					return new Movie(IMDbMovieID,rating,generos,orden);
				}
			} catch (NoSePudoPersistirElModeloException
					| NoExisteLaOntologiaException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;

	}



	/**Obtiene el perfil-ontologia de un usuario
	 * @param idUser el id del usuario
	 * @param conf configuracion del sistema
	 */
	public static OntModel getUserModel(int idUser, Configuration conf) throws NoSePudoPersistirElModeloException, NoExisteLaOntologiaException {
		try{

			URI file = new URI("file:///"+conf.getONTOLOGYUSER()+idUser+".owl");
			File f = new File(file);

			OntModel base = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM);

			//si el modelo existe lo retorno
			if (f.exists()) {
				base.read("file:///"+conf.getONTOLOGYUSER()+idUser+".owl", "RDF/XML");
				return base;
			}else{ //si no existe,se crea a partir de OntologiaIMDB.owl				
                           	base.read("file:///"+conf.getONTOLOGY(), "RDF/XML");
				return base;

			}				
		}catch(URISyntaxException e) {
			System.out.println("UserOntologyManager.getUserModel() URISyntaxException Error: "+e.getMessage());
		} 
		return null;		
	}
        public static OntModel getStereotypeModel(int idEstereotype, Configuration conf) throws NoSePudoPersistirElModeloException, NoExisteLaOntologiaException {
		try{

			URI file = new URI("file:///"+conf.getONTOLOGYSTEREOTYPE()+idEstereotype+".owl");
			File f = new File(file);

			OntModel base = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM);

			//si el modelo existe lo retorno
			if (f.exists()) {
				base.read("file:///"+conf.getONTOLOGYSTEREOTYPE()+idEstereotype+".owl", "RDF/XML");
				return base;
			}else{ //si no existe,se crea a partir de OntologiaIMDB.owl				
                           	base.read("file:///"+conf.getONTOLOGY(), "RDF/XML");
				return base;

			}				
		}catch(URISyntaxException e) {
			System.out.println("UserOntologyManager.getUserModel() URISyntaxException Error: "+e.getMessage());
		} 
		return null;		
	}

        
	public static OntModel getMovieModel(Configuration conf) throws NoSePudoPersistirElModeloException, NoExisteLaOntologiaException {
		
	try{

			URI file = new URI("file:///"+conf.getMOVIESIMDB());
			File f = new File(file);

			OntModel base = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM);

			//si el modelo existe lo retorno
			if (f.exists()) {
				base.read("file:///"+conf.getMOVIESIMDB(), "RDF/XML");
				return base;
			}else{ //si no existe,se crea a partir de OntologiaIMDB.owl				
                           	System.err.println("BASE DE DATOS DE PELICULAS NO EXISTE");
                                base.read("file:///"+conf.getONTOLOGY(), "RDF/XML");
				return base;

			}				
		}catch(URISyntaxException e) {
			System.out.println("UserOntologyManager.getUserModel() URISyntaxException Error: "+e.getMessage());
		} 
		return null;		
	}


	/**Agrega todas los ObjectProperties definidos en la pelicula IMDB a la Ontologia.<br>
	 * <ul>
	 * <li> <code>Year</code>
	 * <li> <code>Director</code>
	 * <li> <code>Actor</code>
	 * <li> <code>Writer</code>
	 * 
	 * @param movieIMDB Pelicula que contiene los atributos que se agregaran a la Ontologia.
	 * @param movieOntology Instancia de la pelicula <code>movieIMDB</code> en la Ontologia.
	 * @param model es el perfil-ontologia
	 * @param NS el namespace de la ontologia
	 * @see ObjectProperty
	 * @see OntModel
	 * @see Individual
	 */
	private static void addAllIndividuals(MovieIMDB movieIMDB, Individual movieOntology, OntModel model, String NS) {

		OntClass yearClass = model.getOntClass(NS+"Year");
		DatatypeProperty dtp = model.getDatatypeProperty(NS+"hasDepicted");
		ObjectProperty op = model.getObjectProperty(NS+"hasYear"),
				opi = model.getObjectProperty(NS+"isYearOf");
		Individual year = model.createIndividual(NS+"_"+(String)movieIMDB.getYear(), yearClass);
		year.setPropertyValue(dtp, model.createTypedLiteral(movieIMDB.getYear()));
		year.addProperty(opi, movieOntology);
		movieOntology.addProperty(op,year);		

		LinkedList<String> ll;

		ll = movieIMDB.getCrew().getDirectors();
		addIndividuals(movieOntology, model, NS, ll, "Director", "hasName");

		ll = movieIMDB.getCrew().getWriters();
		addIndividuals(movieOntology, model, NS, ll, "Writer", "hasName");

		ll = movieIMDB.getCast().getActors();
		addIndividuals(movieOntology, model, NS, ll, "Actor", "hasName");

	}

	/**Agrega los elementos de <code>ll</code> a la propiedad <code>dataTypeProperty</code>
	 * que relaciona la instancia <code>movieOntology</code> con la clase <code>class1</code>.
	 * 
	 * @param movieOntology Instancia de la pelicula en la Ontologia
	 * @param ll LinkedList que contiene los elementos a agregar.
	 * @param class1 Clase definida en la Ontologia base OntologyIMDB.owl
	 * @param dataTypeProperty DatatypeProperty definida en la Ontologia base OntologyIMDB.owl
	 */
	private static void addIndividuals(Individual movieOntology, OntModel model, String NS, LinkedList<String> ll, String class1, String dataTypeProperty) {		

		int numElems = ll.size();		

		OntClass dcClass = model.getOntClass( NS + class1);

		for (int i=0;i<numElems;i++) {
			DatatypeProperty dtp = model.getDatatypeProperty(NS + dataTypeProperty);
			ObjectProperty op = model.getObjectProperty(NS+"has"+class1);
			ObjectProperty opi = model.getObjectProperty(NS+"is"+class1+"Of");
			String name = noSpaceName(ll.get(i));			
			Individual dci = model.createIndividual(NS+name, dcClass);
			dci.setPropertyValue(dtp, model.createTypedLiteral(ll.get(i)));
			dci.addProperty(opi, movieOntology);
			movieOntology.addProperty(op,dci);			
		}

	}

	/** Procesa la cadena <code>n1</code> que representa el nombre de una persona, 
	 * quitandole los espacios a la cadena.
	 *  Esto es creado para una comoda visualizacion de los nombres
	 * 
	 * @param n1 Cadena de texto a procesar.
	 * @return La misma cadena <code>n1</code> sin espacios.
	 */
	private static String noSpaceName(String n1) {

		StringTokenizer s = new StringTokenizer(n1);
		String s1 = "";
		while (s.hasMoreTokens())
			s1 = s1.concat(s.nextToken());

		return s1;
	}





}
